// @tags: [
//   requires_non_retryable_writes,
// ]

// Test index key generation with duplicate values addressed by array index and
// object field.  SERVER-2902

const collNamePrefix = 'jstests_indexu_';
let collCount = 0;
let t = db.getCollection(collNamePrefix + collCount++);
t.drop();

const dupDoc = {
    _id: 0,
    a: [{'0': 1}]
};  // There are two 'a.0' fields in this doc.
const dupDoc2 = {
    a: [{'1': 1}, 'c']
};
const noDupDoc = {
    a: [{'1': 1}]
};

// Test that we can't index dupDoc.
assert.commandWorked(t.save(dupDoc));
assert.commandFailedWithCode(t.createIndex({'a.0': 1}), 16746);

// Test that we can fail gracefully when dupDoc has a large array padded with nulls.
// Index is based on max padding constant in mongo/db/update/path_support.h
assert.commandWorked(t.update({_id: 0}, {$set: {'a.1500001': 1}}));
assert.commandFailedWithCode(t.createIndex({'a.0': 1}), 16746);

t.remove({});
assert.commandWorked(t.createIndex({'a.0': 1}));
assert.writeError(t.save(dupDoc));

// Test that we can't index dupDoc2.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
assert.commandWorked(t.save(dupDoc2));
assert.commandFailedWithCode(t.createIndex({'a.1': 1}), 16746);

t.remove({});
assert.commandWorked(t.createIndex({'a.1': 1}));
assert.writeError(t.save(dupDoc2));

// Test that we can index dupDoc with a different index.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.createIndex({'a.b': 1});
assert.commandWorked(t.save(dupDoc));

// Test number field starting with hyphen.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.createIndex({'a.-1': 1});
assert.commandWorked(t.save({a: [{'-1': 1}]}));

// Test number field starting with zero.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.createIndex({'a.00': 1});
assert.commandWorked(t.save({a: [{'00': 1}]}));

// Test multiple array indexes
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.createIndex({'a.0': 1, 'a.1': 1});
assert.commandWorked(t.save({a: [{'1': 1}]}));
assert.writeError(t.save({a: [{'1': 1}, 4]}));

// Test that we can index noDupDoc.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save(noDupDoc);
assert.commandWorked(t.createIndex({'a.0': 1}));
assert.commandWorked(t.createIndex({'a.1': 1}));

t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.createIndex({'a.0': 1});
t.createIndex({'a.1': 1});
assert.commandWorked(t.save(noDupDoc));

// Test that we can query noDupDoc.
assert.eq(1, t.find({'a.1': 1}).hint({'a.1': 1}).itcount());
assert.eq(1, t.find({'a.1': 1}).hint({$natural: 1}).itcount());
assert.eq(1, t.find({'a.0': {'1': 1}}).hint({'a.0': 1}).itcount());
assert.eq(1, t.find({'a.0': {'1': 1}}).hint({$natural: 1}).itcount());

// Check multiple nested array fields.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save({a: [[1]]});
assert.commandWorked(t.createIndex({'a.0.0': 1}));
assert.eq(1, t.find({'a.0.0': 1}).hint({$natural: 1}).itcount());
assert.eq(1, t.find({'a.0.0': 1}).hint({'a.0.0': 1}).itcount());

// Check where there is a duplicate for a partially addressed field but not for a fully addressed
// field.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save({a: [[1], {'0': 1}]});
assert.commandFailed(t.createIndex({'a.0.0': 1}));

// Check where there is a duplicate for a fully addressed field.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
assert.commandWorked(t.save({a: [[1], {'0': [1]}]}));
assert.commandFailedWithCode(t.createIndex({'a.0.0': 1}), 16746);

// Two ways of addressing parse to an array.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save({a: [{'0': 1}]});
assert.commandFailedWithCode(t.createIndex({'a.0.0': 1}), 16746);

// Test several key depths - with same arrays being found.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save({a: [{'0': [{'0': 1}]}]});
assert.commandFailedWithCode(t.createIndex({'a.0.0.0.0.0.0': 1}), 16746);
assert.commandFailedWithCode(t.createIndex({'a.0.0.0.0.0': 1}), 16746);
assert.commandFailedWithCode(t.createIndex({'a.0.0.0.0': 1}), 16746);
assert.commandFailedWithCode(t.createIndex({'a.0.0.0': 1}), 16746);
assert.commandFailedWithCode(t.createIndex({'a.0.0': 1}), 16746);
assert.commandFailedWithCode(t.createIndex({'a.0': 1}), 16746);
assert.commandWorked(t.createIndex({'a': 1}));

// Two prefixes extract docs, but one terminates extraction before array.
t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save({a: [{'0': {'c': []}}]});
assert.commandFailedWithCode(t.createIndex({'a.0.c': 1}), 16746);

t = db.getCollection(collNamePrefix + collCount++);
t.drop();
t.save({a: [[{'b': 1}]]});
assert.eq(1, t.find({'a.0.b': 1}).itcount());
t.createIndex({'a.0.b': 1});
assert.eq(1, t.find({'a.0.b': 1}).itcount());